
/*!
 * \file      grid.cpp
 * \brief     Grid implementation.
 * \author    Duriez Franck
 * \author    Carton Paul-Henri
 * \version   0.2
 */

#include "assert_macros.h"
#include "constant.h"
#include "grid.h"

Grid::Grid(QSFMLWidget *displayWidget, QObject *parent, float *dt) : /*{{{*/
	    SquareContainer(displayWidget, parent, dt, Vector2i(GRID_WIDTH,GRID_HEIGHT)),
		m_background(sf::Shape::Rectangle(0, 0,
	                                      GRID_WIDTH_f,
	                                      GRID_HEIGHT_f,
	                                      GRID_BACKGROUND_COLOR,
	                                      OUTLINE,
	                                      OUTLINE_COLOR)),
	    destroyFullLines(&Grid::destroyFullLinesStartingProcess,
	                     &Grid::destroyFullLinesProcess,
	                     &Grid::destroyFullLinesStoppingProcess,
	                     this),
	    m_shape(NULL),
	    m_square_in_destruction_number(0)
{
	// Set the background at the right position.
	setBackground();
} /*}}}*/

Grid::~Grid() /*{{{*/
{
} /*}}}*/

void Grid::setBackground() /*{{{*/
{
	m_background.SetPosition(GRID_POS_X, GRID_POS_Y);
} /*}}}*/

void Grid::moveRowDown(Vector2i coord) /*{{{*/
{
	for (int j = coord.y; j > 0  ; j--)
	{
		// Swap recursively Squares from coord to the end of the row to move it down
		swap(Vector2i(coord.x,j),Vector2i(coord.x,j-1));
	
		// Format the Squares moved
		if (getObject(Vector2i(coord.x, j)))
		{
			format(Vector2i(coord.x, j));
		}
	}
} /*}}}*/

bool Grid::isLineFull(int line) const throw() /*{{{*/
{
	bool isFull = true;

	for (int i = 0; i < m_size.x; i++) {
	    isFull = isFull && getObject(Vector2i(i, line));
	}

	return isFull;
} /*}}}*/

bool Grid::isALineFull() const throw() /*{{{*/
{
	bool isFull = false;

	for (int j = 0; j < m_size.y; j++)
	{
	    isFull = isFull || isLineFull(j);
	}

	return isFull;
} /*}}}*/

/************
 * DRAWABLE *
 ************/
/*{{{*/
void Grid::draw() const /*{{{*/
{
	// First, the background is drawn.
	displayer->Draw(m_background);

	// Then, the Squares.
	forAllObjects(&Square::draw);

	// And finally, the Shape.
	if (m_shape) m_shape->draw();
} /*}}}*/

void Grid::update() /*{{{*/
{
	// We need to be sure that there are always a shape in the
	// Grid, otherwise it could induce a segmentation fault !
	ASSERT(m_shape, "", "There is no shape in the Grid !");

	m_shape->update();
	destroyFullLines.update();
	forAllObjects(&Drawable::update);

	// full line => destroy it
	// This will need to be reimplemented if we decide to
	// add special effects during line destruction but for
	// the moment, it's sufficient.
	// It's needed to wait that the previous destroy stop
	// before doing it again, otherwise score will raise
	// too much.
	if (!destroyFullLines.isProcessing()) destroyFullLines.restart(false);
} /*}}}*/

std::string Grid::toString() const /*{{{*/
{
	std::ostringstream res;

	for (int j = 0; j < m_size.y; j++) {
	    for (int i = 0; i < m_size.x; i++)
	    {
	        bool tmp = getObject(Vector2i(i,j));
	        res << tmp;
	    }
	    res << std::endl;
	}
	return res.str();
} /*}}}*/
/*}}}*/

/*********
 * SHAPE *
 *********/
/*{{{*/
void Grid::setShape(Shape* ashape) throw(TetrisException::UnexpectedShape, TetrisException::ShapeOwnershipConflict) /*{{{*/
{
	// We need to be sure that there is no Shape in the Grid
	// before changing the pointer value, otherwise we could have
	// memory leaks.
	ASSERT(!m_shape, "", "There is already a shape in the Grid !");
	
	if (m_shape) throw TetrisException::UnexpectedShape("There is already a Shape in the Grid !");
	
	// Now we can assign the pointer.
	m_shape = ashape;

	// If ashape is the NULL pointer, we need to exit this function
	// otherwise a segmentation fault will happen.
	if (!ashape)
	{
	    DEBUGGER("no shape !");
	    return;
	}

	// This assertion is a little bit crazy in our case, but in case
	// were there are several Grids, we need to be sure that ashape
	// is not already in an other Grid, otherwise the structure of the
	// data could be compromise.
	ASSERT(!ashape->m_grid,"","shape already in a grid !");

	if (ashape->m_grid) throw TetrisException::ShapeOwnershipConflict("Shape already in a Grid !");

	// Now we can set this as the Grid of ashape
	ashape->setGrid(this);
	ashape->setParent(this);
	ashape->setPositionInGrid(Vector2i(m_size.x/2 - ashape->m_size.x/2, 0));
	ashape->setPosition(Vector2f(getPosition().x + (m_size.x/2 - ashape->m_size.x/2) * SQUARE_SIZE,
	                             getPosition().y));
} /*}}}*/

Shape* Grid::getShape() const /*{{{*/
{
	return m_shape;
} /*}}}*/

bool Grid::moveTheShapeDown() /*{{{*/
{
	// If there is not Shape or if the Shape is already
	// moving down, we don't need to do anything.
	if (!m_shape || m_shape->moveDown.isProcessing()) return false;

	// We test if the Shape touch a Square from the bottom.
	// In this case the Grid absorb the Shape, otherwise the
	// Shape is taken down.
	if (m_shape->isASquareDown()) /*{{{*/
	{
		try
		{
			transfertSquareFromShapeToGrid();
		}
		catch (TetrisException::UnexpectedSquare)
		{
			emit gameOver();
			return false;
		}

		emit shapeAbsorbed();
		return false;
	} /*}}}*/

	m_shape->moveDown.start();
	return true;
} /*}}}*/
/*}}}*/

/***********
 * SQUARES *
 ***********/
/*{{{*/
void Grid::transfertSquareFromShapeToGrid() throw(TetrisException::ExpectedShape, TetrisException::UnexpectedSquare) /*{{{*/
{
	ASSERT(m_shape, "", "There is no Shape in the Grid !");

	if (!m_shape) throw TetrisException::ExpectedShape("There is no Shape in the Grid !");

	try
	{
		for (int i = 0; i < m_shape->m_size.x; i++) {
		    for (int j = 0; j < m_shape->m_size.y; j++)
		    {
		        Square* tmpsquare = m_shape->getObject(Vector2i(i,j));
		        if (tmpsquare) setObject(Vector2i(m_shape->getPositionInGrid().x + i,
		                                          m_shape->getPositionInGrid().y + j),
		                                 tmpsquare);
		    }
		}
	}
	catch (TetrisException::UnexpectedSquare e)
	{
		std::cout << "You loose !" << std::endl;
		throw e;
	}

	delete m_shape;
	m_shape = NULL;
} /*}}}*/

void Grid::format(Vector2i coord) const throw(TetrisException::ExpectedSquare) /*{{{*/
{
	// To avoid seg fault it must be a Square at coord
	ASSERT(getObject(coord), "", "No Square at coord !");

	Square* current_square = getObject(coord);

	if (!current_square) throw(TetrisException::ExpectedSquare("In Grid::format : No Square at coord !"));

	current_square->setPosition(Vector2f(getPosition().x + coord.x * SQUARE_SIZE + SQUARE_SIZE/2,
	                                     getPosition().y + coord.y * SQUARE_SIZE + SQUARE_SIZE/2));
	current_square->setCenter(Vector2f(SQUARE_SIZE/2,SQUARE_SIZE/2));
	current_square->setRotation(((int)(current_square->getRotation()/90.f))*90.f);
} /*}}}*/

void Grid::declareDeadObject(Vector2i coord) /*{{{*/
{
	// Put the pointer at coord to NULL
	m_objects[coord.y * m_size.x + coord.x] = (Square*) NULL;

	// Decrease the number of Squares in destruction
	m_square_in_destruction_number--;

	// Finally move the row down
	moveRowDown(coord);
} /*}}}*/
/*}}}*/

/****************
 *   ACTIONS    *
 ****************/
/*{{{*/
void Grid::destroyFullLinesStartingProcess() /*{{{*/
{
	// Number of lines destoyed
	int linesdestroyed = 0;

	for (int j = 0; j < m_size.y; j++) {
		if (isLineFull(j))
		{
			++linesdestroyed;
			for (int i = 0; i < m_size.x; i++)
			{
				Square* tmp = getObject(Vector2i(i,j));
				if (!tmp->destroy.isProcessing())
				{
					tmp->destroy.start();
					++m_square_in_destruction_number;
				}
			}
		}
	}

	// Transmit the number of lines destroyed
	emit linesDestroyed(linesdestroyed);
} /*}}}*/

void Grid::destroyFullLinesProcess() /*{{{*/
{
	if (m_square_in_destruction_number > 0)
	{
		destroyFullLines.maintain();
	}
} /*}}}*/

void Grid::destroyFullLinesStoppingProcess() /*{{{*/
{
	m_square_in_destruction_number = 0;
} /*}}}*/
/*}}}*/

